"""Console-based Update Service for system version management.

This service replaces BLE communication with console logging for system updates.
All update operations are logged to the console for monitoring and debugging.
"""

import json
import logging
import asyncio
import time
from typing import Dict, Optional, Callable, Any, List
from pathlib import Path
from datetime import datetime
import sys

# Add project root to path for imports
sys.path.insert(0, str(Path(__file__).parent.parent.parent))

from src.services.version_manager import VersionManager, UpdateError
from src.services.download_manager import DownloadManager, DownloadState, DownloadProgress
from src.services.install_manager import InstallManager, InstallState, InstallProgress, RollbackTrigger
from src.services.failure_detector import FailureDetector


logger = logging.getLogger(__name__)


class ConsoleUpdateService:
    """Console-based service for system updates without BLE communication."""
    
    def __init__(self, 
                 version_manager: Optional[VersionManager] = None,
                 download_manager: Optional[DownloadManager] = None,
                 install_manager: Optional[InstallManager] = None,
                 failure_detector: Optional[FailureDetector] = None):
        """Initialize the console update service.
        
        Args:
            version_manager: VersionManager instance (creates default if None)
            download_manager: DownloadManager instance (creates default if None)
            install_manager: InstallManager instance (creates default if None)
            failure_detector: FailureDetector instance (creates default if None)
        """
        self.version_manager = version_manager or VersionManager()
        self.download_manager = download_manager or DownloadManager()
        self.install_manager = install_manager or InstallManager()
        self.failure_detector = failure_detector or FailureDetector()
        self.update_state = "IDLE"
        self.update_progress = 0
        
        # Current operation tracking
        self.current_download_id: Optional[str] = None
        self.current_install_package: Optional[str] = None
        self.current_operation: Optional[str] = None
        self.operation_start_time: Optional[datetime] = None
        
        # Error tracking
        self.error_codes = self._init_error_codes()
        self.request_id_counter = 0
        
        # Setup callbacks
        self.install_manager.set_progress_callback(self._install_progress_callback)
        self.install_manager.set_rollback_callback(self._rollback_callback)
        self.failure_detector.set_failure_callback(self._failure_callback)
        self.failure_detector.set_health_callback(self._health_callback)
        
        # Load version info on startup
        self._load_initial_version()
        
        # Console logging setup
        self._setup_console_logging()
    
    def _setup_console_logging(self):
        """Setup enhanced console logging for update operations."""
        console_handler = logging.StreamHandler(sys.stdout)
        console_handler.setLevel(logging.INFO)
        
        formatter = logging.Formatter(
            '%(asctime)s - [UPDATE-SERVICE] - %(levelname)s - %(message)s'
        )
        console_handler.setFormatter(formatter)
        
        # Add handler to the logger if not already present
        if not any(isinstance(h, logging.StreamHandler) for h in logger.handlers):
            logger.addHandler(console_handler)
            logger.setLevel(logging.INFO)
    
    def _init_error_codes(self) -> Dict[int, str]:
        """Initialize error codes mapping."""
        return {
            # General errors (1000-1099)
            1000: "UNKNOWN_ERROR",
            1001: "INVALID_COMMAND", 
            1002: "INVALID_PARAMETERS",
            1003: "OPERATION_TIMEOUT",
            1004: "PERMISSION_DENIED",
            1005: "OPERATION_IN_PROGRESS",
            
            # Download errors (2000-2099)
            2000: "DOWNLOAD_FAILED",
            2001: "NETWORK_UNREACHABLE",
            2002: "CHECKSUM_MISMATCH",
            2003: "INSUFFICIENT_STORAGE",
            2004: "DOWNLOAD_CANCELLED",
            2005: "DOWNLOAD_TIMEOUT",
            
            # Install errors (3000-3099)
            3000: "INSTALL_FAILED",
            3001: "PACKAGE_CORRUPTED",
            3002: "DEPENDENCY_MISSING",
            3003: "SERVICE_START_FAILED", 
            3004: "ROLLBACK_REQUIRED",
            3005: "INSTALL_TIMEOUT",
            
            # System errors (4000-4099)
            4000: "SYSTEM_ERROR",
            4001: "RESOURCE_EXHAUSTED",
            4002: "SERVICE_UNAVAILABLE",
            4003: "HARDWARE_FAILURE",
            4004: "CRITICAL_SERVICE_DOWN",
        }
    
    def _load_initial_version(self):
        """Load version information on service startup."""
        try:
            version_info = self.version_manager.get_version_info()
            print(f"[SYSTEM-UPDATE] Service initialized with app version: {version_info['current']['app']}")
            logger.info(f"Update service initialized with app version: {version_info['current']['app']}")
        except Exception as e:
            print(f"[SYSTEM-UPDATE] ERROR: Failed to load initial version: {e}")
            logger.error(f"Failed to load initial version: {e}")
    
    def _generate_request_id(self) -> str:
        """Generate unique request ID."""
        self.request_id_counter += 1
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        return f"req_{timestamp}_{self.request_id_counter:03d}"
    
    def _format_response(self, status: str, data: Any = None, error_code: int = 0, 
                        error_message: str = "", request_id: str = None) -> Dict:
        """Format standard response structure."""
        return {
            "status": status,
            "error_code": error_code,
            "error_message": error_message,
            "data": data or {},
            "timestamp": datetime.now().isoformat(),
            "request_id": request_id or self._generate_request_id()
        }
    
    def _log_to_console(self, message: str, level: str = "INFO", data: Optional[Dict] = None):
        """Log update operations to console with structured format."""
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        console_msg = f"[{timestamp}] [SYSTEM-UPDATE] [{level}] {message}"
        
        if data:
            console_msg += f" | Data: {json.dumps(data, separators=(',', ':'))}"
        
        print(console_msg)
        
        # Also log to logger
        if level == "ERROR":
            logger.error(message)
        elif level == "WARNING":
            logger.warning(message)
        else:
            logger.info(message)
    
    async def get_version_info(self) -> Dict:
        """Get enhanced version information.
        
        Returns:
            Dictionary with version information
        """
        try:
            version_info = self.version_manager.get_version_info()
            
            # Get update history (last 5)
            update_history = getattr(self.version_manager, 'get_update_history', lambda: [])()[:5]
            
            # Enhanced response format
            response = {
                "current": {
                    "app": version_info["current"]["app"],
                    "ble": version_info["current"]["ble"], 
                    "system": version_info["current"]["system"],
                    "build": datetime.now().strftime("%Y%m%d-%H%M%S"),
                    "commit_hash": "a1b2c3d4"
                },
                "latest": {
                    "app": version_info["latest"]["app"],
                    "available": version_info["latest"]["available"],
                    "release_notes": "Bug fixes and improvements" if version_info["latest"]["available"] else None,
                    "download_size": 52428800 if version_info["latest"]["available"] else None,
                    "checksum": "sha256:abc123..." if version_info["latest"]["available"] else None
                },
                "update_history": update_history,
                "compatibility": {
                    "min_ble_version": "1.2.0",
                    "requires_reboot": True
                },
                "system_info": {
                    "uptime_seconds": int(time.time() - getattr(self, '_start_time', time.time())),
                    "architecture": "aarch64",
                    "kernel_version": "5.15.0-rpi"
                }
            }
            
            self._log_to_console("Version info retrieved", data=response)
            return response
            
        except Exception as e:
            error_response = self._format_response("error", error_code=4000, error_message=str(e))
            self._log_to_console(f"Error retrieving version info: {e}", "ERROR")
            return error_response
    
    async def handle_update_command(self, command: str, params: Dict = None) -> Dict:
        """Handle update command with console logging.
        
        Args:
            command: Update command
            params: Command parameters
            
        Returns:
            Response dictionary
        """
        if params is None:
            params = {}
        
        self._log_to_console(f"Received update command: {command}", data=params)
        
        try:
            if command.upper() == "CHECK":
                # Check for updates
                latest = self.version_manager.check_latest_version()
                self.update_state = "CHECKING"
                self._log_progress("CHECKING", 0, "Checking for updates...")
                
                # Simulate check delay
                await asyncio.sleep(1)
                
                if latest['available']:
                    self.update_state = "UPDATE_AVAILABLE"
                    message = f"Update available: {latest['app']}"
                else:
                    self.update_state = "UP_TO_DATE"
                    message = "System is up to date"
                
                self._log_progress(self.update_state, 100, message)
                response = {"status": "success", "state": self.update_state, "latest": latest}
                self._log_to_console(f"Update check completed: {message}", data=response)
                return response
            
            elif command.upper() == "DOWNLOAD":
                # Start real download
                download_url = params.get('url')
                filename = params.get('filename')
                checksum = params.get('checksum')
                checksum_type = params.get('checksum_type', 'sha256')
                
                if not download_url:
                    response = {"status": "error", "message": "URL required"}
                    self._log_to_console("Download failed: URL required", "ERROR")
                    return response
                
                try:
                    # Start download with progress callback
                    download_id = await self.download_manager.start_download(
                        url=download_url,
                        filename=filename,
                        checksum=checksum,
                        checksum_type=checksum_type,
                        progress_callback=self._download_progress_callback
                    )
                    
                    self.current_download_id = download_id
                    self.update_state = "DOWNLOADING"
                    
                    response = {
                        "status": "success", 
                        "state": "DOWNLOADING",
                        "download_id": download_id
                    }
                    self._log_to_console(f"Download started: {download_url}", data={"download_id": download_id})
                    return response
                    
                except Exception as e:
                    response = {"status": "error", "message": str(e)}
                    self._log_to_console(f"Failed to start download: {e}", "ERROR")
                    return response
            
            elif command.upper() == "INSTALL":
                # Start real installation
                package_path = params.get('package_path')
                force = params.get('force', False)
                
                if not package_path:
                    response = {"status": "error", "message": "Package path required"}
                    self._log_to_console("Install failed: Package path required", "ERROR")
                    return response
                
                # Check if package file exists
                if not Path(package_path).exists():
                    response = {"status": "error", "message": "Package file not found"}
                    self._log_to_console(f"Install failed: Package file not found: {package_path}", "ERROR")
                    return response
                
                try:
                    # Start installation in background
                    self.current_install_package = package_path
                    self.update_state = "INSTALLING"
                    
                    # Start installation task
                    asyncio.create_task(self._run_installation(package_path, force))
                    
                    response = {
                        "status": "success", 
                        "state": "INSTALLING",
                        "package": package_path
                    }
                    self._log_to_console(f"Installation started: {package_path}", data={"force": force})
                    return response
                    
                except Exception as e:
                    response = {"status": "error", "message": str(e)}
                    self._log_to_console(f"Failed to start installation: {e}", "ERROR")
                    return response
            
            elif command.upper() == "GET_STATUS":
                # Get current update status
                status_data = {
                    "state": self.update_state,
                    "progress": self.update_progress,
                    "current_download": self.current_download_id,
                    "current_install": self.current_install_package,
                    "message": self._get_state_message()
                }
                
                self._log_to_console("Status requested", data=status_data)
                return {"status": "success", "data": status_data}
            
            elif command.upper() == "HEALTH_CHECK":
                # Perform immediate health check
                try:
                    health_status = await self.failure_detector.manual_health_check()
                    response = self._format_response("success", data={"health_status": health_status})
                    self._log_to_console("Health check completed", data=health_status)
                    return response
                except Exception as e:
                    response = self._format_response("error", error_code=4000, error_message=str(e))
                    self._log_to_console(f"Health check failed: {e}", "ERROR")
                    return response
            
            else:
                response = {"status": "error", "message": f"Unknown command: {command}"}
                self._log_to_console(f"Unknown command received: {command}", "ERROR")
                return response
                
        except Exception as e:
            response = {"status": "error", "message": str(e)}
            self._log_to_console(f"Error handling command {command}: {e}", "ERROR")
            return response
    
    def _get_state_message(self) -> str:
        """Get user-friendly message for current state."""
        messages = {
            "IDLE": "Ready for updates",
            "CHECKING": "Checking for updates...",
            "UPDATE_AVAILABLE": "Update available",
            "UP_TO_DATE": "System is up to date",
            "DOWNLOADING": f"Downloading update... {self.update_progress}%",
            "VERIFYING": "Verifying update package...",
            "INSTALLING": f"Installing update... {self.update_progress}%",
            "RESTARTING": "Restarting services...",
            "VALIDATING": "Validating installation...",
            "COMPLETE": "Update completed successfully",
            "FAILED": "Update failed",
            "ROLLING_BACK": "Rolling back to previous version...",
            "RESTORED": "System restored to previous version"
        }
        return messages.get(self.update_state, "Unknown state")
    
    async def _download_progress_callback(self, progress: DownloadProgress):
        """Callback for download progress updates."""
        try:
            # Update internal state
            self.update_progress = int(progress.progress_percent)
            
            # Map download states to update states
            if progress.state == DownloadState.DOWNLOADING:
                self.update_state = "DOWNLOADING"
            elif progress.state == DownloadState.VERIFYING:
                self.update_state = "VERIFYING"
            elif progress.state == DownloadState.COMPLETE:
                self.update_state = "DOWNLOAD_COMPLETE"
                self.current_download_id = None
            elif progress.state == DownloadState.FAILED:
                self.update_state = "DOWNLOAD_FAILED"
                self.current_download_id = None
            elif progress.state == DownloadState.CANCELLED:
                self.update_state = "CANCELLED"
                self.current_download_id = None
            elif progress.state == DownloadState.PAUSED:
                self.update_state = "PAUSED"
            
            # Log progress to console instead of sending BLE notification
            progress_data = {
                "state": self.update_state,
                "progress": round(progress.progress_percent, 1),
                "speed": f"{progress.speed_mbps:.2f} MB/s",
                "eta": progress.eta_formatted,
                "downloaded": progress.downloaded_bytes,
                "total": progress.total_bytes
            }
            
            self._log_to_console(
                f"Download progress: {progress.progress_percent:.1f}% - {progress.speed_mbps:.2f} MB/s",
                data=progress_data
            )
                
        except Exception as e:
            self._log_to_console(f"Error in download progress callback: {e}", "ERROR")
    
    async def _install_progress_callback(self, progress: InstallProgress):
        """Callback for installation progress updates."""
        try:
            # Update internal state
            self.update_progress = int(progress.progress_percent)
            
            # Map install states to update states
            if progress.state == InstallState.VALIDATING:
                self.update_state = "VALIDATING"
            elif progress.state == InstallState.BACKUP_CREATING:
                self.update_state = "BACKUP_CREATING"
            elif progress.state == InstallState.SERVICE_STOPPING:
                self.update_state = "SERVICE_STOPPING"
            elif progress.state == InstallState.INSTALLING:
                self.update_state = "INSTALLING"
            elif progress.state == InstallState.CONFIGURING:
                self.update_state = "CONFIGURING"
            elif progress.state == InstallState.SERVICE_STARTING:
                self.update_state = "SERVICE_STARTING"
            elif progress.state == InstallState.VALIDATING_INSTALL:
                self.update_state = "VALIDATING_INSTALL"
            elif progress.state == InstallState.COMPLETE:
                self.update_state = "INSTALL_COMPLETE"
                self.current_install_package = None
            elif progress.state == InstallState.FAILED:
                self.update_state = "INSTALL_FAILED"
                self.current_install_package = None
            elif progress.state == InstallState.ROLLING_BACK:
                self.update_state = "ROLLING_BACK"
            elif progress.state == InstallState.RESTORED:
                self.update_state = "RESTORED"
                self.current_install_package = None
            elif progress.state == InstallState.ROLLBACK_FAILED:
                self.update_state = "ROLLBACK_FAILED"
                self.current_install_package = None
            
            # Log progress to console instead of sending BLE notification
            progress_data = {
                "state": self.update_state,
                "stage": progress.stage,
                "progress": round(progress.progress_percent, 1),
                "current_step": progress.current_step,
                "total_steps": progress.total_steps,
                "elapsed": f"{progress.elapsed_seconds:.0f}s",
                "eta": progress.estimated_time_formatted
            }
            
            if progress.error_message:
                progress_data["error"] = progress.error_message
            
            self._log_to_console(
                f"Install progress: {progress.stage} - {progress.current_step} ({progress.progress_percent:.1f}%)",
                data=progress_data
            )
                
        except Exception as e:
            self._log_to_console(f"Error in install progress callback: {e}", "ERROR")
    
    async def _rollback_callback(self, trigger: RollbackTrigger, reason: str):
        """Callback for rollback events."""
        try:
            self._log_to_console(f"Rollback triggered: {trigger.value} - {reason}", "WARNING")
            
            # Update state
            self.update_state = "ROLLING_BACK"
            
            # Log rollback event to console
            rollback_data = {
                "trigger": trigger.value,
                "reason": reason,
                "timestamp": time.time()
            }
            
            self._log_to_console("Rollback initiated", data=rollback_data)
                
        except Exception as e:
            self._log_to_console(f"Error in rollback callback: {e}", "ERROR")
    
    async def _failure_callback(self, failure_event, diagnostic_report):
        """Callback for failure detection events."""
        try:
            self._log_to_console(
                f"System failure detected: {failure_event.failure_type.value} - {failure_event.message}",
                "ERROR"
            )
            
            # Log failure details to console
            failure_data = {
                "failure_type": failure_event.failure_type.value,
                "severity": failure_event.severity.value,
                "message": failure_event.message,
                "diagnostic_id": getattr(diagnostic_report, 'diagnostic_id', None),
                "rollback_recommendation": diagnostic_report.rollback_recommendation,
                "timestamp": failure_event.timestamp.isoformat()
            }
            
            self._log_to_console("Failure diagnostic report", data=failure_data)
                
        except Exception as e:
            self._log_to_console(f"Error in failure callback: {e}", "ERROR")
    
    async def _health_callback(self, health_data):
        """Callback for health status updates."""
        try:
            self._log_to_console(f"Health status update: {health_data['overall_status']}")
            
            # Log health update to console
            health_summary = {
                "overall_status": health_data['overall_status'],
                "timestamp": health_data['timestamp'],
                "components_count": len(health_data.get('components', {}))
            }
            
            self._log_to_console("System health update", data=health_summary)
                
        except Exception as e:
            self._log_to_console(f"Error in health callback: {e}", "ERROR")
    
    async def _run_installation(self, package_path: str, force: bool = False):
        """Run installation in background."""
        try:
            self._log_to_console(f"Starting installation: {package_path}")
            success = await self.install_manager.install_package(package_path, force)
            
            if success:
                self._log_to_console(f"Installation completed successfully: {package_path}")
            else:
                self._log_to_console(f"Installation failed: {package_path}", "ERROR")
                
        except Exception as e:
            self._log_to_console(f"Installation error: {e}", "ERROR")
    
    def _log_progress(self, state: str, progress: int, message: str):
        """Log progress update to console.
        
        Args:
            state: Current update state
            progress: Progress percentage (0-100)
            message: Status message
        """
        self.update_state = state
        self.update_progress = progress
        
        progress_data = {
            "state": state,
            "progress": progress,
            "message": message
        }
        
        self._log_to_console(f"Progress update: {message}", data=progress_data)
    
    def get_current_status(self) -> Dict:
        """Get current update status without BLE communication.
        
        Returns:
            Current status dictionary
        """
        return {
            "state": self.update_state,
            "progress": self.update_progress,
            "current_download": self.current_download_id,
            "current_install": self.current_install_package,
            "message": self._get_state_message(),
            "timestamp": datetime.now().isoformat()
        }
    
    def start_service(self):
        """Start the console update service."""
        self._log_to_console("Console Update Service started")
        print("=" * 60)
        print("SYSTEM UPDATE SERVICE - CONSOLE MODE")
        print("BLE communication disabled - using console logging")
        print("=" * 60)


# Example standalone usage for testing
if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO)
    
    async def test_console_service():
        """Test the console update service."""
        service = ConsoleUpdateService()
        
        # Start service
        service.start_service()
        
        # Test version info
        version_info = await service.get_version_info()
        print(f"\n[TEST] Version info retrieved")
        
        # Test update check
        response = await service.handle_update_command("CHECK")
        print(f"\n[TEST] Check response: {response['status']}")
        
        # Test status
        status = service.get_current_status()
        print(f"\n[TEST] Current status: {status['state']}")
        
        await asyncio.sleep(2)  # Let any background tasks complete
    
    # Run test
    asyncio.run(test_console_service())